﻿using System.Xml.XPath;
using System.Diagnostics;
using System;
using Engine.Sgml.XPath;
using Engine.Dtd.Schema;

namespace Engine.Sgml {
    /// <summary>
    /// Represents a single element in the SGML document.
    /// </summary>
    public partial class SElement : SObject, IXPathNavigable {
        internal SObjectCollection<SAttribute> Attributes { get; private set; }
        internal SObjectCollection<SElement> Nodes { get; private set; }

        /// <summary>
        /// Initialize a new instance of the SElement class.
        /// </summary>
        internal SElement() {
            Name = SName.Empty;
            Nodes = new SObjectCollection<SElement>(16, () => new SElement());
            Attributes = new SObjectCollection<SAttribute>(3, () => new SAttribute());
            }

        /// <summary>
        /// Initialize a new instance of the SElement class with specified name and type.
        /// </summary>
        /// <param name="name">A <see cref="SName"/> object that represents the name of the element.</param>
        /// <param name="type">A <see cref="SgmlNodeType"/> enumerator that specifies the node type for this element.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="name"/> is null.</exception>
        public SElement(SName name, SgmlNodeType type)
            : this() {
            Guard.ArgumentNotNull(name, "name");
            Name = name;
            NodeType = type;
            }

        /// <summary>
        /// Initialize a new instance of the SElement class with name, type and DTD element declaration.
        /// </summary>
        /// <param name="name">A <see cref="SName"/> object that represents the name of this element.</param>
        /// <param name="declaration">A <see cref="DElementDeclaration"/> object from a <see cref="DtdSchema"/> that is associated with a current SGML document.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="name"/> or <paramref name="declaration"/> is null.</exception>
        public SElement(SName name, DElementDeclaration declaration)
            : this(name, SgmlNodeType.Element) {
            Guard.ArgumentNotNull(declaration, "declaration");
            DName = declaration.Name;
            Declaration = declaration;
            }

        /// <summary>
        /// Gets a value indicating whether this element contains no content.
        /// </summary>
        public bool IsEmpty { get; internal set; }

        /// <summary>
        /// Gets the DTD declaration of this element.
        /// </summary>
        public DElementDeclaration Declaration { get; internal set; }

        /// <summary>
        /// Creates a duplicate of this node.
        /// </summary>
        /// <param name="deep">true to recursively clone the sub tree under the specified node; false to clone only the node itself.</param>
        /// <returns>The cloned node.</returns>
        public SElement Clone(bool deep) {
            SElement clone = new SElement();
            clone.CopyFrom(this);
            clone.IsEmpty = IsEmpty;
            clone.Declaration = Declaration;
            foreach (SAttribute attribute in Attributes) {
                SAttribute a = attribute.Clone();
                clone.Attributes.Add(a);
                a.Parent = clone;
                }
            if (deep)
                foreach (SElement element in Nodes) {
                    SElement c = element.Clone(true);
                    clone.Nodes.Add(c);
                    c.Parent = clone;
                    }
            return clone;
            }

        /// <summary>
        /// Creates a duplicate of this node.
        /// </summary>
        /// <returns>The cloned node.</returns>
        public SElement Clone() {
            return Clone(false);
            }

        /// <summary>
        /// Resets the members of this class to their default values.
        /// </summary>
        internal sealed override void Clear() {
            base.Clear();
            IsEmpty = false;
            Attributes.Clear();
            Declaration = null;
            Nodes.Clear();
            }

        /// <summary>
        /// Creates an XPathNavigator for this element.
        /// </summary>
        /// <returns>An XPathNavigator object.</returns>
        public XPathNavigator CreateNavigator() {
            return new SgmlNavigator(this);
            }

        internal DElementType ElementType {
            get {
                if (DName == null)
                    return DElementType.Unknown;
                else if (Declaration == null)
                    return DElementType.None;
                else
                    return Declaration.ElementType;
                }
            }

        internal void InitializeDeclaration(DtdSchema schema, bool caseSensitiveTags) {
            Debug.Assert(schema != null);
            // Получить DTD имя
            // Выполнить поиск декларации узла
            DName = schema.Names[LocalName, caseSensitiveTags];
            Declaration = schema.GetElementDeclaration(DName);
            // Если имя узла есть во множестве имен схемы документа
            // но декларация для узла не найдена
            // Причина: Имя узла равно имени атрибута в текущей схеме
            if (object.ReferenceEquals(Declaration, null) && !object.ReferenceEquals(DName, null)) {
                Declaration = schema.GetElementDeclaration(LocalName.ToUpperInvariant());
                if (Declaration == null) {
                    Declaration = schema.GetElementDeclaration(LocalName.ToLowerInvariant());
                    if (Declaration == null)
                        DName = null;
                    else
                        DName = Declaration.Name;
                    }
                else
                    DName = Declaration.Name;
                }
            }
        }
    }
